A website document, which is designed to be viewed in a web browser, comprises HyperText Markup Language (HTML) markup and various assets, which are parsed by the web browser and laid out to form a visible web page. The assets include images, Cascading Style Sheet (CSS) documents, JavaScript documents, as well as any embedded media. The common practice, and industry standard, is to load the HTML of the page, and then parse other assets to alter the layout of that HTML, place images as needed, and set “listeners” (triggers) on various Document Object Model (DOM) elements in order to react to user input. The DOM is an Application Programming Interface (API) for valid HTML and well-formed XML documents. It defines the logical structure of documents and the way a document or data is accessed and manipulated. This procedure typically causes the website to be parsed as a monolithic document with JavaScript and CSS resources loaded either in the header of the DOM and/or near the bottom, so that scripts can attach listeners to, or otherwise alter, referenced DOM elements.
FIG. 1 illustrates a typical coding structure for a conventional simple website. The typical method of programming a website includes HTML mark up to set up the structure of the site. On line 7 of the code in FIG. 1, a CSS document is referenced, which causes the web browser to request the CSS document from the server. Only after the browser loads the CSS document does the browser continue to parse the rest of the HTML mark up. On line 13 of the code in FIG. 1, a JavaScript document is referenced. This reference causes the web browser to request the JavaScript document from the web server, and the code contained in the JavaScript document is compiled and run by a JavaScript engine in the browser. At that point, if the JavaScript code calls for a listener to be attached to a particular DOM element in the HTML mark up, a reference for the particular DOM element is made by the element's ID attribute, or by the element's class attribute. This requires a poll of every DOM element in the page to take place in order to find any and all elements that match the reference set by the JavaScript code. If the page is short, this polling is minimal. However, if the page is very long, and several elements are referenced by the JavaScript code, this polling becomes very expensive in terms of performance. Additionally, if care is not taken to ensure that elements have unique ID's and classes, a greedy reference by the JavaScript will cause the wrong elements to be affected, and the website functionality will become unstable and unreliable.
In the case of a conventional “static” website, when the user clicks on a link, for example a link from a home page to a sub-page of the website, the browser destroys the DOM instance for the home page and unloads it from memory. The browser then sends an HTTP request to the server requesting information from the server for the sub-page. The browser then creates a new DOM instance for the sub-page. Thereafter, this process of downloading resources, creating a new DOM instance, and then destroying the DOM instance and starting over is repeated for each new page requested by the user.
FIG. 2 is a message flow diagram illustrating the flow of messages between a user 10, a client browser 11, and a website server 12 in a method of operating a conventional static website. At step 13, the user types in a web address directing the browser to go to the website, somesite.com/home. At step 14, the browser requests information from the server for the home page of somesite.com by sending an HTTP request to the server. At step 15, the server processes the request and returns to the browser, a string containing resources such as full HTML, CSS documents, JavaScript code, and all content data associated with the home page. At step 16, the browser interprets the HTML code and creates a new DOM instance. In optional steps 17 and 18, the browser may request and receive from the server, cacheable resources that have not yet been downloaded.
At step 19, the browser interprets and executes the JavaScript code, and displays the somesite.com home page to the user. At step 20, the user browses the home page, and at step 21, attempts to view content that was not included in the original content data received from the server. For example, the user may click on a link to a sub-page of the somesite.com website. In response, the browser destroys the DOM instance for the home page and unloads it from memory at step 22. At step 23, the browser then sends an HTTP request to the server requesting information from the server for the sub-page of somesite.com. The process then repeats, with the browser creating a new DOM instance for the sub-page. Thereafter, this process of downloading resources, creating a new DOM instance, and then destroying the DOM instance and starting over is repeated for each new page requested by the user.
In order to break the monolithic nature of a website and cause the content to be delivered in a modular fashion, a templating system is often used. In such a system, reusable HTML markup code is used as a template, and a data source is used to populate various portions of the template, before being rendered as needed within the webpage. There are basically two types of templating systems—those that run on the server side, and those that run on the client side. In both instances, a data source is used to populate various fields within an HTML template, and the resulting HTML code is returned to the web browser for display.
FIG. 3A illustrates a typical coding structure for producing a simple server-side template. Server-side templates take data from a data source, populate the template, and then piece together the rendered results to create a single web page, before sending it to the browser for display. Server-side templates employ a templating engine in the server-side code to populate templates that are coded in HTML and either a proprietary templating language or the corresponding server-side code.
FIG. 3B illustrates a typical coding structure for replacing the tags in the server-side template of FIG. 3A with data. Server-side code such as PHP: Hypertext Preprocessor (PHP) takes data from a data source and makes a call to the templating engine to replace the tags in the template with the data. Thus, in the above example, a typical templating engine is invoked, provided with variables from a data source, and then rendered to return the HTML to the client browser.
Server-side templating engines separate presentation development from data modeling and business logic development, and provide a code base that is easier to maintain and change as needed. Server-side templating engines also allow for HTML template code to be reused, which reduces the amount of code that has to be written, which in turn reduces the chances for bugs or breaking functionality when changes are made. However, server-side templating engines return full HTML documents to the client, and much of the HTML being transferred in the response from the server is likely to be duplicated blocks of code with different values in the fields. For example, the following HTML is repeated three times with different values:
<div>Bill</div><div>Bob</div><div>Joe</div>
The HTML for each element is a simple <div></div> tag group, and it is repeated with different first names inserted. However, because the HTML was rendered in the server-side code, the entire string of HTML code is sent to the client. In this small example, the amount of code is negligible. However, on large websites with extremely large numbers of repeating objects, this is very inefficient and requires much more bandwidth than should be necessary.
Client-side templates send a copy of the template to the client's browser, along with structured data to serve as a data source, and the template is populated with JavaScript in the browser. This method greatly reduces bandwidth usage between the client and server on sites that have a large amount of content with duplicated formatting.
Client-side templates are typically managed by templating engines that are written in JavaScript. Therefore, they run within the JavaScript virtual machine built into the web browser. In this scenario, only one copy of the template is sent in the response from the web server, along with a string of structured data to be used for populating the template. The templating engine in the web browser is then called in JavaScript code within the document to parse the data source, replace the tags appropriately within the template, and then render the template as many times as needed within the browser.
FIG. 4 illustrates a typical coding structure for producing a client-side template using the same example as shown above, but implemented in client-side templating. While this example comprises more lines of code than the server-side example, it does not grow in size as the number of elements in the data source grows. In other words, if the “names” variable contained 100 names, the only change would be that one line would be longer, and no other lines of code are needed. Additionally, if the data source was referencing a function (as shown in the commented code line), the amount of data items could be extremely large without additional code being written.
Most importantly, however, is the fact that this is the total amount of code that would be required to be sent in the web server response, and the data string could be sent in separate calls to the server as needed.
Thus, using client-side templates provides a large decrease in bandwidth required for a large website containing several blocks of repeated markup sections. Additionally, since the HTML template is already loaded into the browser, additional calls to the server for more data can be made asynchronously to populate a site with fresh content, or additional content, in response to time passing or user interaction of the page.
Client-side templating engines are typically written in JavaScript and are available with open source as well as commercial licensing. While they produce the advantages stated above, none are as robust as their server-side counterparts with regard to nesting, embedded logic, and the capability to pass complex data structures.
FIG. 5 illustrates a simplified example of the use of client-side templates in a conventional website. This example uses Handlebars.js, which is a popular templating engine. Those skilled in the art will readily recognize this coding structure. Although such a templating system assists with modularity, as well as separating presentation design from the data source, on a very large site with a large number of modular sections, collisions in class definitions and the increased polling required to attach listeners to specific DOM elements negatively impact scaling, performance, and reliability.
FIG. 6 illustrates a typical coding structure for the use of a common client-side templating engine, which allows a reusable template to be populated from a data source. In this example, an HTML template containing replaceable tags for “title” and “body” is included in a script block. The templating engine then compiles the template. Then, a data source is defined, which may be static data or a web service that returns the data in a usable format. The example includes hard-coded static data for demonstration purposes only. Those tags in the compiled template are replaced by data contained in the data source object, based on the names for each of the object elements. For example, {{title}} in the template may be replaced by “My New Post”, when that is the value of the “title” element in the data source object. HTML is rendered from the compiled template with replaced values and then the “div” element with the ID attribute of “content_div” is populated with the resulting HTML.
This method of programming allows for reuse of presentation mark up code (HTML), and separates the data from the presentation for maintainability and readability of the code. However, when scaled to larger sites, performance degrades quickly because the compilation of templates and the bloat in the third-party templating engine library is expensive in terms of processing resources. Additionally, the excess polling problem mentioned previously is not addressed. Once the template is compiled, values are replaced, and HTML is generated, the JavaScript must search the DOM for the correct element to populate with the results. Again, this causes increasingly poor performance as the size of the site grows and the number of DOM elements increases. The possibility of class collisions also still exists.
When building an interactive web application, client-side code written in JavaScript is typically used to attach event listeners onto DOM elements, run specific callback functions when those events are triggered, and manipulate the DOM elements as necessary to provide feedback for user interaction. In all but the smallest and simplest applications, it is often necessary to include more than one script tag in the DOM, which includes the JavaScript code to manage the application. This is especially the case when creating modular applications in which each module contains a script block to control the module to which the script block is attached.
FIG. 7A illustrates a typical coding structure for managing DOM elements defined just prior to a script block. The code contains two sections of code, which includes HTML markup and two JavaScript blocks. In this example, the code in the two script blocks will run sequentially as the browser loads them into the DOM. This method of development is typical and does not pose problems in simple web applications with very little user interaction required. However, as web applications have become more complex, a need to reference which script block's code is currently running has developed. One example of this is when scripts are being loaded dynamically in repeated modules on a single page, and each script block is required to act on DOM elements only within the module to which the script block is attached.
FIG. 7B illustrates a typical coding structure for loading scripts dynamically in repeated modules on a single page. In this example, the first module will load into the DOM and the code in the module's script block will run, inserting the word “Contents” into the first <div> element with the class name of “container”, as expected. However, when the page continues loading, the second module will load into the DOM and the code in the second script tag will run. It will insert the word “Contents2” into all elements in the DOM that have the class of “container.” This will not only insert the content into the div tag contained as part of the second module, it will also replace the contents in the div tag which is part of the first module, because it also has the class name of “container.” Additionally, running the third module's code will result in the content of all three div elements being “Content3.”
The problem shown in this example can be avoided by having a unique ID for each div tag and writing the JavaScript to reference the tag by the ID attribute. However, this limits code reuse and many other advantages gained by using a modular, template-driven website composition.
FIG. 8 illustrates a coding structure for an alternative way to load scripts dynamically in repeated modules on a single page. In this example, the problem is solved by making the scripts aware of themselves, and referencing only the DOM elements relative to each script. However, although this method solves the problem when loading modules sequentially, it does not work when modules are loaded dynamically or asynchronously. Also, the currentScript property is likely to return a reference to the incorrect DOM element should another script be running asynchronously elsewhere in the page. The currentScript property is also not supported in many older browsers.